package org.opens.sql;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import org.opens.db.DynamicDataSourceUtil;
import org.opens.db.constant.DbType;
import org.opens.exception.BusinessException;
import org.opens.sql.constant.DeleteMode;
import org.opens.sql.constant.GenSqlType;
import org.opens.sql.genConfig.GenMapperPublicConfig;
import org.opens.sql.genConfig.GenSqlConfig;
import org.opens.sql.genConfig.TableConfig;
import org.opens.sql.genConfig.TombstoneConfig;
import org.opens.sql.genHandle.GenHandle;
import org.opens.sql.genHandle.GenHandleFactory;
import org.opens.util.DatabaseConnectInfoEntity;
import org.opens.util.FileUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GenSqlHelper extends GenSqlTemplate<GenSqlConfig> {

    private static Log log = LogFactory.get(GenSqlHelper.class);

    private static final Pattern safePattern = Pattern.compile("^[a-z|A-Z]:([\\\\|/]+)?$");

    @Override
    public void before(GenSqlConfig genSqlConfig) {
        before_SF_paramCheck(genSqlConfig);

        before_SF_requiredParamInit(genSqlConfig);

        before_SF_clearGenPath(genSqlConfig);
    }

    @Override
    public void process(GenSqlConfig genSqlConfig) {
        process_SP_genTable(genSqlConfig);
    }

    @Override
    public void afterThrowing(Exception e, GenSqlConfig genSqlConfig) {
        afterThrowing_SF_boxRuntimeException(e);
    }

    @Override
    public void afterReturning(GenSqlConfig genSqlConfig) {
        afterReturning_SF_closeDbConnection();

        afterReturning_SF_genZip(genSqlConfig);

        afterReturning_SF_openGenPathFolder(genSqlConfig);
    }

    /**
     * before的paramCheck步骤.
     * - SP表示sftpOf的首尾字母
     */
    private void before_SF_paramCheck(GenSqlConfig genSqlConfig) {
        if (genSqlConfig == null) {
            throw new BusinessException("空的生成配置无法生成代码!");
        }

        if (genSqlConfig.getDatabaseConnectInfo() == null) {
            throw new BusinessException("空的数据库配置无法生成代码!");
        }

        if (StrUtil.isBlank(genSqlConfig.getGenPath())) {
            throw new BusinessException("代码生成路径不可为空!");
        }

        if (CollectionUtil.isEmpty(genSqlConfig.getGenTables()) && CollectionUtil.isEmpty(genSqlConfig.getTableConfigs())) {
            throw new BusinessException("没有配置要生成的表!");
        }
    }

    /**
     * 必须参数初始化
     * @param genSqlConfig 代码生成配置
     */
    private void before_SF_requiredParamInit(GenSqlConfig genSqlConfig) {
        // 如果没有指定数据库类型, 则从数据库url中提取
        if (genSqlConfig.getDatabaseConnectInfo().getDbType() == null) {
            DbType dbType = DynamicDataSourceUtil.extractDatabaseTypeFromURL(genSqlConfig.getDatabaseConnectInfo().getUrl());
            if (dbType == null) {
                throw new BusinessException("未知的配置数据库类型, 且自动提取数据库类型失败, 生成结束!");
            }
            genSqlConfig.getDatabaseConnectInfo().setDbType(dbType);
        }

        DbType dbType = genSqlConfig.getDatabaseConnectInfo().getDbType();

        // mysql才需要去从jdbc中提取库名
        if (DbType.MYSQL == dbType) {
            // 如果没有指定数据库名, 就从数据库url中提取出来
            if (StrUtil.isEmpty(genSqlConfig.getDatabaseConnectInfo().getDatabaseName())) {
                String databaseName = DynamicDataSourceUtil.extractDatabaseNameFromURL(genSqlConfig.getDatabaseConnectInfo().getUrl());
                if (StrUtil.isEmpty(databaseName)) {
                    throw new BusinessException("未配置数据库连接, 且自动提取数据库名失败, 生成结束!");
                }
                genSqlConfig.getDatabaseConnectInfo().setDatabaseName(databaseName);
            }
        }

        // 对要生成的表进行去重
        List<TableConfig> tableConfigs = genSqlConfig.getTableConfigs();
        if (CollectionUtil.isNotEmpty(tableConfigs)) {
            // 根据tableName字段进行去重
            genSqlConfig.setTableConfigs(CollectionUtil.distinct(tableConfigs, TableConfig::getTableName, true));
        }

        // 对要生成的表进行去重
        List<String> genTables = genSqlConfig.getGenTables();
        if (CollectionUtil.isNotEmpty(genTables)) {
            genSqlConfig.setGenTables(CollectionUtil.distinct(genTables));
        }

        // 默认使用物理删除
        if (genSqlConfig.getGenMapperPublicConfig().getDeleteMode() == null) {
            genSqlConfig.getGenMapperPublicConfig().setDeleteMode(DeleteMode.PHYSICAL_DELETION);
        }

        // 如果设置了逻辑删除但是没有指定策略就使用默认的策略
        if (genSqlConfig.getGenMapperPublicConfig().getDeleteMode() == DeleteMode.TOMBSTONE) {
            if (genSqlConfig.getGenMapperPublicConfig().getTombstoneConfig() == null) {
                genSqlConfig.getGenMapperPublicConfig().setTombstoneConfig(TombstoneConfig.getDefault());
            }
        }
    }

    /**
     * 清空生成路径
     * @param genSqlConfig 配置
     */
    private void before_SF_clearGenPath(GenSqlConfig genSqlConfig) {
        String genPath = genSqlConfig.getGenPath();

        // 安全目录检查
        Assert.notBlank(genPath, "param path can not be empty!");
        Matcher matcher = safePattern.matcher(genPath);
        if (matcher.matches()) {
            throw new IllegalArgumentException("危险的生成路径, 清空行为危险, 请修改清空参数或者生成目录: " + genPath);
        }

        // genPath必须是目录
        if (!cn.hutool.core.io.FileUtil.isDirectory(genPath)) {
            throw new IllegalArgumentException("path " + genPath + " not is dir!");
        }

        // genPath必须是空的, 或者只包含src目录和code.zip文件
        File file = new File(genPath);
        // 生成目录和压缩文件排除在外
        List<String> ignoreFileNames = new ArrayList<>();
        // 如果设置为生成前不清空, 则不能把之前生成的算入忽略的
        if (genSqlConfig.isGenPreClear()) {
            ignoreFileNames.add("src");
            ignoreFileNames.add("code.zip");
        }
        File[] files = file.listFiles(x -> !ignoreFileNames.contains(x.getName()));
        if (files == null) {
            throw new IllegalArgumentException("配置的生成目录不存在: " + genPath);
        }
        if (files.length > 0) {
            throw new IllegalArgumentException("配置的生成目录非空, 只能包含下列文件: " + JSON.toJSONString(ignoreFileNames) + ", 请手动清空生成目录后重试或更换生成目录, 当前生成目录: " + genPath);
        }
        // 清空genPath
        cn.hutool.core.io.FileUtil.clean(genSqlConfig.getGenPath());
    }

    /**
     * 遍历genTable开始生成代码
     * @param genSqlConfig 代码生成配置
     */
    private void process_SP_genTable(GenSqlConfig genSqlConfig) {
        log.info("生成开始!");

        List<TableConfig> tableConfigs = genSqlConfig.getTableConfigs();
        List<String> genTables = genSqlConfig.getGenTables();
        if (CollectionUtil.isNotEmpty(tableConfigs)) {
            for (TableConfig tableConfigItem : tableConfigs) {
                DatabaseConnectInfoEntity databaseConnectInfo = genSqlConfig.getDatabaseConnectInfo();
                databaseConnectInfo.setTableName(tableConfigItem.getTableName());

                process_SP_genTable_SP_genJavaCode(genSqlConfig);
            }
        } else if (CollectionUtil.isNotEmpty(genTables)) {
            for (String genTable : genTables) {
                DatabaseConnectInfoEntity databaseConnectInfo = genSqlConfig.getDatabaseConnectInfo();
                databaseConnectInfo.setTableName(genTable);

                process_SP_genTable_SP_genJavaCode(genSqlConfig);
            }
        } else {
            throw new IllegalArgumentException("未配置要生成的表信息, 程序终止, 请配置genTables属性或tableConfigs属性!");
        }

        log.info("生成结束!");
    }

    /**
     * 开始生成java代码
     * @param genSqlConfig 代码生成配置
     */
    private static void process_SP_genTable_SP_genJavaCode(GenSqlConfig genSqlConfig) {
        // 生成java代码
        List<GenSqlType> genSqlTypes = genSqlConfig.getGenSqlTypes();
        for (GenSqlType genType : genSqlTypes) {
            GenHandle genHandle = GenHandleFactory.getInstance(genType);
            DatabaseConnectInfoEntity databaseConnectInfo = genSqlConfig.getDatabaseConnectInfo();
            try {
                log.info("数据库: {}, 表: {}, {} 开始生成!", databaseConnectInfo.getDatabaseName(), databaseConnectInfo.getTableName(), genType.getName());
                genHandle.genCodeValid(genSqlConfig);
                genHandle.genCode(genSqlConfig);

            } catch (Exception e) {
                log.info("数据库: {}, 表: {}, {} 生成异常: ", databaseConnectInfo.getDatabaseName(), databaseConnectInfo.getTableName(), genType.getName(), e.getMessage());
                throw e;
            }
            log.info("数据库: {}, 表: {}, {} 生成结束!", databaseConnectInfo.getDatabaseName(), databaseConnectInfo.getTableName(), genType.getName());
        }
    }

    /**
     * 关闭数据库连接资源
     */
    private void afterThrowing_SF_closeDbConnection() {
        // 关闭数据库连接对象
        DynamicDataSourceUtil.close();
    }

    /**
     * 关闭数据库连接资源
     */
    private void afterReturning_SF_closeDbConnection() {
        // 关闭数据库连接对象
        DynamicDataSourceUtil.close();
    }

    /**
     * 生成zip
     * @param genSqlConfig 代码生成配置
     */
    private void afterReturning_SF_genZip(GenSqlConfig genSqlConfig) {
        // 生成zip
        if (genSqlConfig.isGenResult2zip()) {
            String zipFileDescPath = FileUtil.pathSplicing(genSqlConfig.getGenPath(), "code.zip");
            String zipFileSrcPath = FileUtil.pathSplicing(genSqlConfig.getGenPath(), "src");
            log.info("压缩文件{}生成开始!", zipFileDescPath);
            try {
                // ZipUtil.zip(zipFileSrcPath, zipFileDescPath);
                cn.hutool.core.util.ZipUtil.zip(zipFileSrcPath, zipFileDescPath);
                log.info("压缩文件{}生成成功!", zipFileDescPath);
            } catch (Exception e) {
                log.info("压缩文件{}生成失败!", zipFileDescPath);
                throw e;
            }
        }
    }

    /**
     * 生成代码后代开生成代码路径
     * @param genSqlConfig 代码生成配置
     */
    private void afterReturning_SF_openGenPathFolder(GenSqlConfig genSqlConfig) {
        // 打开代码生成目录
        if (genSqlConfig.isGenEndOpenGenPath()) {
            FileUtil.openFolder(genSqlConfig.getGenPath());
        }
    }

    /**
     * 包装异常
     * @param e 异常
     */
    private void afterThrowing_SF_boxRuntimeException(Exception e) {
        log.error("afterThrowing", e);
        afterThrowing_SF_closeDbConnection();
        throw new RuntimeException(e);
    }

}
